<template>
  <div class="dashboard-container">
    <div class="app-container">
      <!-- card 带阴影效果的容器 -->
      <el-card class="dept-card">
        <!-- 公司名称
        row 和 col 都是elementUI提供的布局组件
        row：表示行，支持flex布局，只需要给组件添加type=flex即可
        col：表示列表，一行分为24列，可以通过span属性设置每一列占的比例
         -->
        <TreeTool :bool="false" :data="company" @addEvent="addDept" />
        <!-- 部门列表 -->
        <!--
          tree组件的使用
            data: 渲染的数据源
            default-expand-all:	是否默认展开所有节点 true表示展开，false关闭
            props：用于修改一些默认配置的
              label：设置渲染内容所代表的属性名
          -->
        <el-tree default-expand-all :data="treeData" :props="{label: 'name'}">
          <!-- v-slot="{data}" 这里的data是tree组件通过作用域插槽传回来的数据
          这个数据是treeData数组里面的对象 -->
          <template v-slot="{data}">
            <!-- v-on绑定  简写@ -->
            <TreeTool
              :bool="true"
              :data="data"
              @addEvent="addDept"
              @delEvent="delDept"
              @editEvent="editEPart"
            />
          </template>
        </el-tree>
      </el-card>
      <!-- 新增 部门 弹窗 -->
      <!-- close: 弹窗关闭时触发的事件 -->
      <el-dialog :title="formData.id?'编辑部门':'新增部门'" :visible="showDialog" @close="closeFn">
        <!--
          label: 设置表单旁边的标题内容
          label-width: 设置标题的宽度

          form表单验证，满足以下4个条件即可实现
            1、给form组件绑定model，值为变量对象
            2、给form绑定rules，这个属性用于定义校验规则
            3、给form-item组件绑定prop属性、prop 属性设置为需校验的字段名即可。
            4、v-model双向邦定表单

         -->
        <el-form ref="form" :model="formData" :rules="rules" label-width="100px">
          <el-form-item label="部门名称" prop="name">
            <el-input v-model="formData.name" placeholder="1-50个字符" />
          </el-form-item>
          <el-form-item label="部门编码" prop="code">
            <el-input v-model="formData.code" placeholder="1-50个字符" />
          </el-form-item>
          <el-form-item label="部门负责人" prop="manager">
            <el-select v-model="formData.manager">
              <el-option
                v-for="item in userList"
                :key="item.id"
                :label="item.username"
                :value="item.username"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="部门介绍" prop="introduce">
            <el-input v-model="formData.introduce" type="textarea" placeholder="1-300个字符" />
          </el-form-item>
        </el-form>
        <template v-slot:footer>
          <!-- 利用row组件的flex布局实现 按钮居中 -->
          <el-row type="flex" justify="center">
            <el-button @click="closeFn">取消</el-button>
            <el-button type="primary" @click="submit">确定</el-button>
          </el-row>
        </template>
      </el-dialog>
    </div>
  </div>
</template>

<script>
import TreeTool from './components/tree-tool.vue'
import { getDepartments, addDepartment, delDepartment, departmentsDetail,
  updateDepartment } from '@/api/department'
import { userSimple } from '@/api/employees'
export default {
  components: { TreeTool },
  data() {
    // 校验部门编码
    const checkCode = (rule, val, callback) => {
      // rule: 当前表单元素的信息和校验规则
      // val: 当前表单元素的内容
      // callback: 是一个函数，当我们直接调用callback的时候表示校验通过，如果调用callback并传入参数的话表示校验不通过。
      // callback(true) // callback只要传入了参数就表示校验不通过（无论什么参数）
      // callback() // 不传任何参数就是表示校验通过
      /**
       * 如何判断部门编码重复了？
       * 1、获取到当前用户输入的部门编码 --- val
       * 2、获取到其他已经存在的所有部门编码 ----- this.depts
       * */
      // let isRepact = false // false表示没有重复 否则重复了
      // this.depts.forEach(item => {
      //   if (item.code === val) {
      //     isRepact = true
      //   }
      // })
      /**
       * every() 方法用于检测数组所有元素是否都符合指定条件（通过函数提供）。
       *  如果数组中检测到有一个元素不满足，则整个表达式返回 false ，且剩余的元素不会再进行检测。
       *  如果所有元素都满足条件，则返回 true。
       * */

      // 当code等于val的时候isRepact是false(false表示重复了)，否则是true
      // 编辑的时候，需要判断不能跟自己进行比较，所以我们需要分情况来实现部门编码的校验
      // 怎么区分编辑和新增？答：id
      let isRepact
      if (this.formData.id) {
        // 编辑 , 排除自身 ，首先要知道怎么判断两个部门是同一个部门?
        // 答：id 因为后台设计数据的时候，会给每一个部门都添加id，且id值是唯一的。
        // this.depts 是所有的部门列表数据,this.formData是回显的部门数据
        // 编辑的时候formData这个部门是存在在depts数组中的
        // filter之后得到的数组是不包含自身部门的列表数据
        isRepact = this.depts.filter(item => item.id !== this.formData.id).every(item => item.code !== val)
      } else {
        isRepact = this.depts.every(item => item.code !== val)
      }
      if (!isRepact) {
        callback(`部门编码${val}和已存在的编码重名了！`)
      } else {
        callback()
      }
    }
    // 校验部门名称
    const checkName = (rule, val, callback) => {
      // console.log(136, val)
      // 需求：校验同级部门名称不能重复
      /**
       *  1、获取当前输入的部门名称 --- val
       *  2、获取到新部门的所有同级部门名称 ---- 不能直接拿到
       *    2-1、获取到所有的部门列表 ---- this.depts
       *    2-2、根据所有部门列表筛选和“新增的部门”的同级部门列表
       *      2-2-1、如何筛选？答：可以通过pid进行筛选，当两个部门的pid值相同的时候，
       *        说明他们有同一个父部门，从而说明他们是同级部门
       *
      */
      let sameDept
      if (this.formData.id) {
      // 编辑 筛选同级部门  排除自身 再找同级部门
        sameDept = this.depts.filter(item => item.id !== this.formData.id && item.pid === this.formData.pid)
      } else {
      // 新增 筛选同级部门
        sameDept = this.depts.filter(item => item.pid === this.formData.pid)
      }
      // console.log(149, sameDept)
      const isRepact = sameDept.every(item => item.name !== val)
      if (!isRepact) {
        callback(`${val}和已存在的部门名称重名了！`)
      } else {
        callback()
      }
    }
    return {
      company: {
        name: '传智教育',
        manager: '负责人',
        id: ''
      },
      treeData: [],
      showDialog: false, // 控制弹窗显示隐藏, false表示隐藏
      formData: {
        name: '', // 部门名称
        code: '', // 编码
        manager: '', // 负责人
        introduce: '', // 介绍
        pid: '' // 新增部门父级部门的id
      },
      rules: {
        name: [
          { required: true, message: '部门名称不能为空', trigger: 'blur' },
          { min: 1, max: 50, message: '部门编码长度1到50位字符' },
          // 需求：同级部门中，部门名称不能重复
          { validator: checkName, trigger: 'blur' }
        ],
        code: [
          { required: true, message: '部门名称不能为空', trigger: 'blur' },
          { min: 1, max: 50, message: '部门编码长度1到50位字符' },
          // 校验部门编码和已存在的部门编码是否重复，重复则校验不通过，否则通过
          // 这里涉及比较复杂的逻辑，因此可以用到form组件的自定义校验规则进行实现
          { validator: checkCode, trigger: 'blur' }
        ],
        manager: [
          { required: true, message: '部门名称不能为空', trigger: 'blur' },
          { min: 1, max: 50, message: '部门编码长度1到50位字符' }
        ],
        introduce: [
          { required: true, message: '部门名称不能为空', trigger: 'blur' },
          { min: 1, max: 50, message: '部门编码长度1到50位字符' }
        ]
      },
      userList: [], // 部门负责人列表
      depts: [] // 未转化的部门列表数据
    }
  },
  async created() {
    // 获取部门列表
    this.getDepartments()
    // 或员工列表（负责人列表）
    const res = await userSimple()
    // console.log(116, res)
    this.userList = res.data.data
  },
  methods: {
    // 编辑部门
    async editEPart(val) {
      this.showDialog = true
      const res = await departmentsDetail(val.id)
      // console.log(138, res)
      this.formData = res.data.data // 实现回显
    },
    // 删除部门
    delDept(val) {
      // console.log(130, val)
      // 显示询问框
      this.$confirm('是否确定删除 ？', '提示', {
        type: 'warning'
      }).then(async() => {
        // console.log('删除部门')
        await delDepartment(val.id)
        // 刷新页面列表
        this.getDepartments()
      }).catch(() => {
        console.log('点击了取消按钮')
      })
    },
    // 确定 新增
    submit() {
      // 表单验证
      // validate是elementUI中form组件提供的对整个表单进行校验的方法
      this.$refs.form.validate(async bool => {
        // bool 为true表示校验通过，否则校验不通过
        if (bool) {
          // 怎么区分是编辑还是新增 ？答：可以通过formData中有无id进行判断
          // 有id是编辑，没有是新增
          if (this.formData.id) {
            // 编辑
            await updateDepartment(this.formData)
          } else {
          // 调用接口 实现新增
            await addDepartment(this.formData)
          }
          // 关闭弹窗
          this.closeFn()
          // 刷新列表
          this.getDepartments()
        }
      })
    },
    // 关闭弹出
    closeFn() {
      this.showDialog = false
      // 清除数据
      this.formData = {
        name: '', // 部门名称
        code: '', // 编码
        manager: '', // 负责人
        introduce: '', // 介绍
        pid: '' // 新增部门父级部门的id
      }
      // 重置校验规则  ---- 既能重置校验规也能重置表按数据
      this.$refs.form.resetFields()
    },
    // 新增部门
    addDept(val) {
      console.log(128, val)
      // 保存当前点击的那个部门的id
      // 比如：我们为技术部添加子部门，那么此刻pid的值就应该是技术部的id
      this.formData.pid = val.id
      // 控制新增部门弹窗显示
      this.showDialog = true
    },
    // 获取部门列表
    async getDepartments() {
      const res = await getDepartments()
      // console.log(res)
      // 保存公司名称
      this.company.name = res.data.data.companyName
      // 保存未转化的部门列表
      this.depts = res.data.data.depts
      // 保存 列表转树形结构
      this.treeData = this.listToTree(res.data.data.depts, '')
    },
    /**
     * 列表转树形结构
     * 为了列表转树形结构的函数能够实现复用
     * 转换的数据源和一级部门的pid的值都可以改为参数
     * 这样有利于复用
     *  list：要转换的数据源
     *  id: 一级数据的pid的值
     *
     * 这里我们利用递归函数 优化了代码量
     * 满足以下两个条件都可以叫递归函数：
     *  1、函数中调用了自己
     *  2、需要有判断条件 终止代码的执行
     *
     * */
    listToTree(list, id) {
      // treeList 用于保存转换好的嵌套结构列表
      const treeList = []
      // 1、先找一级部门
      list.forEach(item => {
        if (item.pid === id) {
          // item就是一级部门，当部门的pid===""的时候说明该部门为一级部门
          treeList.push(item)
          // // 找二级部门，当部门的pid值等于一级部门的id的是，说明该部门为二级部门，也就是if语句里面item的子部门
          // item.children = [] // 表示为item添加children属性并赋值为空数组
          // list.forEach(val => {
          //   if (val.pid === item.id) {
          //     // val 就是item的子部门，因此我们只需要把val添加到item的children属性中即可
          //     item.children.push(val)
          //   }
          // })
          // 经过观察我们发现 找一级部门的代码和找二级部门的代码 逻辑和判断等等都是一样的
          // 因此我们可以复用找一级部门的代码来实现找二级部门，那如何复用呢？答：只要在这里
          // 重新调用一下listToTree函数即可
          item.children = this.listToTree(list, item.id)
        }
      })
      return treeList
    }
  }
}
</script>

<style lang="scss" scoped>
.dept-card{
  padding: 30px 40px;
  font-size: 14px;
  // elementUI中封装的组件 所有的组件名就是该组件根标签的类名，因为我们可以直接如下使用。
  // 为什么要设计成这样呢？答：为了让使用者更方便修改组件的默认样式
  .el-tree{
    margin-top: 20px;
  }
}
</style>
